/**
 * @description 激活的副作用函数
 * @type {Function | undefined}
 */
let activeEffect;
/**
 * @description 原始数据
 */
const data = { ok: true, text: "Hello, World!" };
/**
 * @description 副作用函数集合
 * @type {WeakMap<any,Map<string,set<Function>>>}
 */
const bucket = new WeakMap();
/**
 * @description 副作用函数栈
 */
const effectStack = [];
/** 
 * @description 任务队列
 */
const jobQueue = new Set();
/**
 * @description 执行任务
 */
const p = Promise.resolve();
/**
 * @description 执行任务队列
 */
let isFlushing = false;
const flushJobs = () => {
  if (isFlushing) {
    return;
  }
  isFlushing = true;
  p.then(() => {
    for (const job of jobQueue) {
      job();
    }
  }).finally(() => {
    isFlushing = false;
  });
};

/**
 * @description 追踪数据
 * @param {*} target
 * @param {*} key
 * @returns 
 */
const track = (target, key) => {
  if (!activeEffect) {
    return target[key];
  }
  let depMaps = bucket.get(target);
  if (!depMaps) {
    bucket.set(target, (depMaps = new Map()));
  }
  let deps = depMaps.get(key);
  if (!deps) {
    depMaps.set(key, (deps = new Set()));
  }
  deps.add(activeEffect);
  activeEffect.deps.push(deps);
};

/**
 * @description 触发副作用函数
 * @param {*} target
 * @param {*} key
 * @returns
 */
const trigger = (target, key) => {
  let depMaps = bucket.get(target);
  if (!depMaps) {
    return;
  }
  let deps = depMaps.get(key);
  if (deps) {
    const effectToRun = new Set();
    deps.forEach((dep) => {
      if (dep !== activeEffect) {
        effectToRun.add(dep);
      }
    });
    effectToRun.forEach((dep) => {
      if (dep.options.schedular) {
        dep.options.schedular(dep);
      } else {
        dep();
      }
    });
  }
};

/**
 * @description 代理对象
 */
const obj = new Proxy(data, {
  get(target, key) {
    track(target, key);
    return target[key];
  },
  set(target, key, value) {
    target[key] = value;
    trigger(target, key);
  },
});

/**
 * @description 清理副作用函数
 * @param {*} effectFn
 */
const cleanup = (effectFn) => {
  for (let index = 0; index < effectFn.deps.length; index++) {
    const deps = effectFn.deps[index];
    deps.delete(effectFn);
  }
};

/**
 * @description 副作用函数
 * @param {Function} fn
 */
const effect = (fn, options = {}) => {
  const effectFn = () => {
    cleanup(effectFn);
    effectStack.push(effectFn);
    activeEffect = effectFn;
    fn();
    effectStack.pop();
    activeEffect = effectStack[effectStack.length - 1];
  };
  effectFn.options = options;
  effectFn.deps = [];
  effectFn();
};

effect(
  () => {
    console.log("effect run");
    document.body.innerHTML = obj.ok ? obj.text : "Hello, Proxy!";
  },
  {
    schedular: (effectFn) => {
      jobQueue.add(effectFn);
      flushJobs();
    },
  }
);

setTimeout(() => {
  obj.ok = false;
}, 1000);
